package ibxm;
import java.nio.ByteOrder;
public class IBXM {
        public static final String VERSION = "ibxm alpha 45 (c)2006 mumart@gmail.com";
        public static final int FP_SHIFT = 15;
        public static final int FP_ONE = 1 << FP_SHIFT;
        public static final int FP_MASK = FP_ONE - 1;
        private int sampling_rate, resampling_quality, volume_ramp_length;
        private int tick_length_samples, current_tick_samples;
        private int[] mixing_buffer, volume_ramp_buffer;
        private Module module;
        private Channel[] channels;
        private int[] global_volume, note;
        private int current_sequence_index, next_sequence_index;
        private int current_row, next_row;
        private int tick_counter, ticks_per_row;
        private int pattern_loop_count, pattern_loop_channel;
        public IBXM( int sample_rate ) {
                System.out.println( VERSION );
                if( sample_rate < 8000 ) {
                        sample_rate = 8000;
                }
                sampling_rate = sample_rate;
                volume_ramp_length = sampling_rate >> 10;
                volume_ramp_buffer = new int[ volume_ramp_length * 2 ];
                mixing_buffer = new int[ sampling_rate / 6 ];
                global_volume = new int[ 1 ];
                note = new int[ 5 ];
                set_module( new Module() );
                set_resampling_quality( 1 );
        }
       
        public void set_module( Module m ) {
                int channel_idx;
                module = m;
                channels = new Channel[ module.get_num_channels() ];
                for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                        channels[ channel_idx ] = new Channel( module, sampling_rate, global_volume );
                }
                set_sequence_index( 0, 0 );
        }
        public void set_resampling_quality( int quality ) {
                resampling_quality = quality;
        }
       
        public int calculate_song_duration() {
                int song_duration;
                set_sequence_index( 0, 0 );
                next_tick();
                song_duration = tick_length_samples;
                while( !next_tick() ) {
                        song_duration += tick_length_samples;
                }
                set_sequence_index( 0, 0 );
                return song_duration;
        }
       
        public void set_sequence_index( int sequence_index, int row ) {
                int channel_idx;
                global_volume[ 0 ] = 64;
                for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                        channels[ channel_idx ].reset();
                        channels[ channel_idx ].set_panning( module.get_initial_panning( channel_idx ) );
                }
                set_global_volume( module.global_volume );
                set_speed( 6 );
                set_speed( module.default_speed );
                set_tempo( 125 );
                set_tempo( module.default_tempo );
                pattern_loop_count = -1;
                next_sequence_index = sequence_index;
                next_row = row;
                tick_counter = 0;
                current_tick_samples = tick_length_samples;
        }
        public void seek( int sample_position ) {
                set_sequence_index( 0, 0 );
                next_tick();
                while( sample_position > tick_length_samples ) {
                        sample_position -= tick_length_samples;
                        next_tick();
                }
                next_tick();
                current_tick_samples = sample_position;
        }
        public void get_audio( byte[] output_buffer, int frames ) {
                boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
               
                int output_idx, mix_idx, mix_end, count, amplitude;
                output_idx = 0;
                while( frames > 0 ) {
                        count = tick_length_samples - current_tick_samples;
                        if( count > frames ) {
                                count = frames;
                        }
                        mix_idx = current_tick_samples << 1;
                        mix_end = mix_idx + ( count << 1 ) - 1;
                        while( mix_idx <= mix_end ) {
                                amplitude = mixing_buffer[ mix_idx ];
                                if( amplitude > 32767 ) {
                                        amplitude = 32767;
                                }
                                if( amplitude < -32768 ) {
                                        amplitude = -32768;
                                }
                               
                                if (bigEndian) {
                                        output_buffer[ output_idx     ] = ( byte ) ( amplitude >> 8 );
                                        output_buffer[ output_idx + 1 ] = ( byte ) ( amplitude & 0xFF );
                                } else {       
                                        output_buffer[ output_idx     ] = ( byte ) ( amplitude & 0xFF );
                                        output_buffer[ output_idx + 1 ] = ( byte ) ( amplitude >> 8 );
                                }
                                output_idx += 2;
                                mix_idx += 1;
                        }
                        current_tick_samples = mix_idx >> 1;
                        frames -= count;
                        if( frames > 0 ) {
                                next_tick();
                                mix_tick();
                                current_tick_samples = 0;
                        }
                }
        }
        private void mix_tick() {
                int channel_idx, mix_idx, mix_len;
                mix_idx = 0;
                mix_len = tick_length_samples + volume_ramp_length << 1;
                while( mix_idx < mix_len ) {
                        mixing_buffer[ mix_idx ] = 0;
                        mix_idx += 1;
                }
                for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                        mix_len = tick_length_samples + volume_ramp_length;
                        channels[ channel_idx ].resample( mixing_buffer, 0, mix_len, resampling_quality );
                }
                volume_ramp();
        }
        private boolean next_tick() {
                int channel_idx;
                boolean song_end;
                for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                        channels[ channel_idx ].update_sample_idx( tick_length_samples );
                }
                tick_counter -= 1;
                if( tick_counter <= 0 ) {
                        tick_counter = ticks_per_row;
                        song_end = next_row();
                } else {
                        for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                                channels[ channel_idx ].tick();
                        }
                        song_end = false;
                }
                return song_end;
        }
       
        private boolean next_row() {
                int channel_idx, effect, effect_param;
                boolean song_end;
                Pattern pattern;
                song_end = false;
                if( next_sequence_index < 0 ) {
                        /* Bad next sequence index.*/
                        next_sequence_index = 0;
                        next_row = 0;
                }
                if( next_sequence_index >= module.get_sequence_length() ) {
                        /* End of sequence.*/
                        song_end = true;
                        next_sequence_index = module.restart_sequence_index;
                        if( next_sequence_index < 0 ) {
                                next_sequence_index = 0;
                        }
                        if( next_sequence_index >= module.get_sequence_length() ) {
                                next_sequence_index = 0;
                        }
                        next_row = 0;
                }
                if( next_sequence_index < current_sequence_index ) {
                        /* Jump to previous pattern. */
                        song_end = true;
                }
                if( next_sequence_index == current_sequence_index ) {
                        if( next_row <= current_row ) {
                                if( pattern_loop_count < 0 ) {
                                        /* Jump to previous row in the same pattern, but not a pattern loop. */
                                        song_end = true;
                                }
                        }
                }
                current_sequence_index = next_sequence_index;
                pattern = module.get_pattern_from_sequence( current_sequence_index );
                if( next_row < 0 || next_row >= pattern.num_rows ) {
                        /* Bad next row.*/
                        next_row = 0;
                }
                current_row = next_row;
                next_row = current_row + 1;
                if( next_row >= pattern.num_rows ) {
                        next_sequence_index = current_sequence_index + 1;
                        next_row = 0;
                }
                for( channel_idx = 0; channel_idx < channels.length; channel_idx++ ) {
                        pattern.get_note( note, current_row * channels.length + channel_idx );
                        effect = note[ 3 ];
                        effect_param = note[ 4 ];
                        channels[ channel_idx ].row( note[ 0 ], note[ 1 ], note[ 2 ], effect, effect_param );
                        switch( effect ) {
                                case 0x0B:
                                        /* Pattern Jump.*/
                                        if( pattern_loop_count < 0 ) {
                                                next_sequence_index = effect_param;
                                                next_row = 0;
                                        }
                                        break;
                                case 0x0D:
                                        /* Pattern Break.*/
                                        if( pattern_loop_count < 0 ) {
                                                next_sequence_index = current_sequence_index + 1;
                                                next_row = ( effect_param >> 4 ) * 10 + ( effect_param & 0x0F );
                                        }
                                        break;
                                case 0x0E:
                                        /* Extended.*/
                                        switch( effect_param & 0xF0 ) {
                                                case 0x60:
                                                        /* Pattern loop.*/
                                                        if( ( effect_param & 0x0F ) == 0 ) {
                                                                /* Set loop marker on this channel. */
                                                                channels[ channel_idx ].pattern_loop_row = current_row;
                                                        }
                                                        if( channels[ channel_idx ].pattern_loop_row < current_row ) {
                                                                /* Marker and parameter are valid. Begin looping. */
                                                                if( pattern_loop_count < 0 ) {
                                                                        /* Not already looping, begin. */
                                                                        pattern_loop_count = effect_param & 0x0F;
                                                                        pattern_loop_channel = channel_idx;
                                                                }
                                                                if( pattern_loop_channel == channel_idx ) {
                                                                        /* Loop in progress on this channel. Next iteration. */
                                                                        if( pattern_loop_count == 0 ) {
                                                                                /* Loop finished. */
                                                                                /* Invalidate current marker. */
                                                                                channels[ channel_idx ].pattern_loop_row = current_row + 1;
                                                                        } else {
                                                                                /* Count must be higher than zero. */
                                                                                /* Loop and cancel any breaks on this row. */
                                                                                next_row = channels[ channel_idx ].pattern_loop_row;
                                                                                next_sequence_index = current_sequence_index;
                                                                        }
                                                                        pattern_loop_count -= 1;
                                                                }
                                                        }
                                                        break;
                                                case 0xE0:
                                                        /* Pattern delay.*/
                                                        tick_counter += ticks_per_row * ( effect_param & 0x0F );
                                                        break;
                                        }
                                        break;
                                case 0x0F:
                                        /* Set Speed/Tempo.*/
                                        if( effect_param < 32 ) {
                                                set_speed( effect_param );
                                                tick_counter = ticks_per_row;
                                        } else {
                                                set_tempo( effect_param );
                                        }
                                        break;
                                case 0x25:
                                        /* S3M Set Speed.*/
                                        set_speed( effect_param );
                                        tick_counter = ticks_per_row;
                                        break;
                        }
                }
                return song_end;
        }
        private void set_global_volume( int volume ) {
                if( volume < 0 ) {
                        volume = 0;
                }
                if( volume > 64 ) {
                        volume = 64;
                }
                global_volume[ 0 ] = volume;
        }
        private void set_speed( int speed ) {
                if( speed > 0 && speed < 256 ) {
                        ticks_per_row = speed;
                }
        }
        private void set_tempo( int bpm ) {
                if( bpm > 31 && bpm < 256 ) {
                        tick_length_samples = ( sampling_rate * 5 ) / ( bpm * 2 );
                }
        }       
        private void volume_ramp() {
                int ramp_idx, next_idx, ramp_end;
                int volume_ramp_delta, volume, sample;
                sample = 0;
                volume_ramp_delta = FP_ONE / volume_ramp_length;
                volume = 0;
                ramp_idx = 0;
                next_idx = 2 * tick_length_samples;
                ramp_end = volume_ramp_length * 2 - 1;
                while( ramp_idx <= ramp_end ) {
                        sample = volume_ramp_buffer[ ramp_idx ] * ( FP_ONE - volume ) >> FP_SHIFT;
                        mixing_buffer[ ramp_idx ] = sample + ( mixing_buffer[ ramp_idx ] * volume >> FP_SHIFT );
                        volume_ramp_buffer[ ramp_idx ] = mixing_buffer[ next_idx + ramp_idx ];
                        sample = volume_ramp_buffer[ ramp_idx + 1 ] * ( FP_ONE - volume ) >> FP_SHIFT;
                        mixing_buffer[ ramp_idx + 1 ] = sample + ( mixing_buffer[ ramp_idx + 1 ] * volume >> FP_SHIFT );
                        volume_ramp_buffer[ ramp_idx + 1 ] = mixing_buffer[ next_idx + ramp_idx + 1 ];
                        volume += volume_ramp_delta;
                        ramp_idx += 2;
                }
        }
}